﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

using umbraco.NodeFactory;

namespace Juzzbott.Ulib
{
	public class UmbracoNode
	{

		#region Properties

		/// <summary>
		/// The base node for the current UmbracoNode
		/// </summary>
		public Node BaseNode { get; set; }

		/// <summary>
		/// The Title of the node.
		/// </summary>
		public string Title { get; set; }

		/// <summary>
		/// The Menu Title of the node.
		/// </summary>
		public string MenuTitle { get; set; }

		/// <summary>
		/// The ID of the node.
		/// </summary>
		public int ID { get; set; }

		/// <summary>
		/// The URL to the node. If the RedirectUrl is set, then the URL will be the URL the page will be redirected to. 
		/// Use the ActualUrl property to override this functionality.
		/// </summary>
		public string Url { get; set; }

		/// <summary>
		/// The actual URL to the umbraco node, regardless of any redirect information.
		/// </summary>
		public string ActualUrl { get; set; }

		/// <summary>
		/// The URL to redirect to should any redirect details be set. 
		/// </summary>
		public string RedirectUrl { get; set; }

		private List<UmbracoNode> _children = null;
		/// <summary>
		/// The list of children for the current umbraco node.
		/// </summary>
		public List<UmbracoNode> Children
		{
			get
			{
				// If the list of children is null, then load it.
				if (_children == null)
				{
					loadChildren();
				}
				return _children;
			}
		}

		#endregion

		#region Constructor

		/// <summary>
		/// Creates a new instance of the UmbracoNode object.
		/// </summary>
		/// <param name="baseNode">The base node to use.</param>
		private UmbracoNode(Node baseNode)
		{

			// Set the properties of the node
			this.BaseNode = baseNode;

			// Load the node
			LoadNode();

		}

		#endregion

		#region Load Node

		/// <summary>
		/// Loads the properties of the base node into the current node.
		/// </summary>
		public void LoadNode()
		{

			// If the base node is null, then exit
			if (BaseNode == null)
			{
				return;
			}

			// Set the ID property
			this.ID = BaseNode.Id;

			// Set the title properties
			string title = GetPropertyValue("title");
			this.Title = (!String.IsNullOrEmpty(title) ? title : BaseNode.Name);

			// Set the menu title property.
			string menuTitle = GetPropertyValue("menuTitle");
			this.MenuTitle = (!String.IsNullOrEmpty(menuTitle) ? menuTitle : this.Title);

			// Set the url
			this.RedirectUrl = getNodeRedirectUrl();
			this.ActualUrl = umbraco.library.NiceUrl(this.ID);
			this.Url = (!String.IsNullOrEmpty(this.RedirectUrl) ? this.RedirectUrl : this.ActualUrl);

		}

		#endregion

		#region Get Node

		/// <summary>
		/// Gets an UmbracoNode by a base Node object.
		/// </summary>
		/// <param name="baseNode">The umbraco.nodeFactory.Node object to use as the base node.</param>
		/// <returns>The UmbracoNode object, or null if baseNode is null.</returns>
		public static UmbracoNode GetNode(Node baseNode)
		{
			// if the base node is null, return null
			if (baseNode == null)
			{
				return null;
			}

			// return a new Umbraco Node based on the base node
			return new UmbracoNode(baseNode);

		}

		/// <summary>
		/// Gets the Umbraco Node by the Node ID.
		/// </summary>
		/// <param name="nodeID">The string based numeric ID of the node to get.</param>
		/// <returns>The Umbraco Node, or null if not found.</returns>
		public static UmbracoNode GetNode(string nodeID)
		{
			// Get the node ID as an integer.
			int intNodeID = 0;
			Int32.TryParse(nodeID, out intNodeID);

			// If the node ID is 0, then return null as the int conversion failed
			if (intNodeID == 0)
			{
				return null;
			}
			else
			{
				// Load the node by node id
				return GetNode(intNodeID);
			}

		}

		/// <summary>
		/// Gets the Umbraco Node by the Node ID.
		/// </summary>
		/// <param name="nodeID">The ID of the node to get.</param>
		/// <returns>The Umbraco Node, or null if not found.</returns>
		public static UmbracoNode GetNode(int nodeID)
		{

			// Get the base node
			Node baseNode = new Node(nodeID);

			// If the base node is null, then return null
			if (baseNode == null)
			{
				return null;
			}

			// Create a new instance of the UmbracoNode and return it
			return new UmbracoNode(baseNode);

		}

		#endregion

		#region IsNotNull

		/// <summary>
		/// Returns true if the node or the base node is not null, otherwise false.
		/// </summary>
		/// <param name="theNode">The node to check.</param>
		/// <returns>True if not null, otherwsie false.</returns>
		public static bool IsNotNull(UmbracoNode theNode)
		{
			// Check to see if the node if null or not
			if (theNode != null && theNode.BaseNode != null)
			{
				return true;
			}
			else
			{
				return false;
			}
		}

		/// <summary>
		/// If the node is null, it will return true, otherwise false if it exists.
		/// </summary>
		/// <param name="theNode">The node to check.</param>
		/// <returns>True if null, otherwise false.</returns>
		public static bool IsNull(UmbracoNode theNode)
		{
			// Returns the inverse of the result of IsNotNull.
			return !IsNotNull(theNode);
		}

		#endregion

		#region GetPropertyValue

		/// <summary>
		/// Gets the property value based on the property name.
		/// </summary>
		/// <param name="propertyAlias">The name (alias) of the property to get the value for.</param>
		/// <returns>The property value, or "" (empty string) if the property does not exist or is empty.</returns>
		public string GetPropertyValue(string propertyAlias)
		{
			return GetPropertyValue(propertyAlias, "");
		}

		/// <summary>
		/// Gets the property value based on the property name.
		/// </summary>
		/// <param name="propertyAlias">The name (alias) of the property to get the value for.</param>
		/// <param name="defaultValue">The value to use if the property does not exist or is empty.</param>
		/// <returns>The property value, or defaultValue if the property does not exist or is empty.</returns>
		public string GetPropertyValue(string propertyAlias, string defaultValue)
		{
			return GetPropertyValue<string>(propertyAlias, defaultValue);
		}

		/// <summary>
		/// Gets the property value based on the property name.
		/// </summary>
		/// <typeparam name="T">The type of object to return the value as.</typeparam>
		/// <param name="propertyAlias">The name (alias) of the property to get the value for.</param>
		/// <param name="defaultValue">The value to use if the property does not exist or is empty.</param>
		/// <returns>The property value, or defaultValue if the property does not exist or is empty.</returns>
		public T GetPropertyValue<T>(string propertyAlias, T defaultValue)
		{

			// Out property to determine if the property exists
			bool exists;

			// Get the property
			Property theProp = (Property)this.BaseNode.GetProperty(propertyAlias, out exists);

			// if the property does not exist, the value is empty or null, then return default value
			if (!exists || String.IsNullOrEmpty(theProp.Value))
			{
				return defaultValue;
			}
			else
			{
				// Get the value
				string value = theProp.Value;

				// Try to convert the value to the given type
				try
				{

					// return the value as the specified type
					T propertyValue = (T)Convert.ChangeType(value, typeof(T));

					// return the value
					return propertyValue;

				}
				catch (Exception)
				{
					// Error converting value, so return default value
					return defaultValue;
				}
			}

		}

		#endregion

		#region Children

		/// <summary>
		/// Loads the list of children in current umbraco node.
		/// </summary>
		private void loadChildren()
		{
			// Get the list of nodes from the base item
			umbraco.NodeFactory.Nodes childNodes = this.BaseNode.Children;

			// If the child nodes is null, just return
			if (childNodes == null)
			{
				return;
			}

			// Instantiate the list of nodes
			_children = new List<UmbracoNode>();

			// Loop through the list of nodes
			foreach (Node childNode in childNodes)
			{

				// Create the umbraco node
				UmbracoNode umbNode = UmbracoNode.GetNode(childNode);

				// If it's not null, add to the child list
				if (UmbracoNode.IsNotNull(umbNode))
				{
					_children.Add(umbNode);
				}

			}

		}

		#endregion

		#region Load List

		/// <summary>
		/// Loads a list of UmbracoNode objects from the raw, comma separated list of IDs.
		/// </summary>
		/// <param name="rawNodeIDs">The comma separated list of node IDs.</param>
		/// <returns>The list of umbraco nodes to retrieve.</returns>
		public static List<UmbracoNode> LoadList(string rawNodeIDs)
		{
			// If the list is null or empty, then exit
			if (String.IsNullOrEmpty(rawNodeIDs))
			{
				return new List<UmbracoNode>();
			}

			// Get the rawNodeIDs as a list of strings
			List<string> nodeIDs = rawNodeIDs.Split(new char[] { ',' }).ToList();

			// return the nodes
			return LoadList(nodeIDs);

		}

		/// <summary>
		/// Gets a list of UmbracoNode objects from a list of Node IDs.
		/// </summary>
		/// <param name="nodeIDs">The list of node id's to return as Umbraco Node objects.</param>
		/// <returns>A list of UmbracoNode objects.</returns>
		public static List<UmbracoNode> LoadList(List<string> nodeIDs)
		{

			// Create the list of ints
			List<int> intNodeIDs = new List<int>();

			// Loop through the list
			foreach (string nodeID in nodeIDs)
			{
				// Wrap it in a try so any non integers are not converted and not added to list
				try
				{

					// Add the node id's to the list of integers
					intNodeIDs.Add(Int32.Parse(nodeID.Trim()));

				}
				catch (Exception) { }

			}

			// Load the list of nodes with int IDs
			return LoadList(intNodeIDs);

		}

		/// <summary>
		/// Gets a list of UmbracoNode objects from a list of Node IDs.
		/// </summary>
		/// <param name="nodeIDs">The list of node id's to return as Umbraco Node objects.</param>
		/// <returns>A list of UmbracoNode objects.</returns>
		public static List<UmbracoNode> LoadList(List<int> nodeIDs)
		{

			// create the list of UmbracoNode objects
			List<UmbracoNode> umbNodes = new List<UmbracoNode>();

			// Loop through the list of nodes
			foreach (int nodeID in nodeIDs)
			{
				// Get the umbraco node
				UmbracoNode theNode = UmbracoNode.GetNode(nodeID);

				// if the node exists, add to the list
				if (UmbracoNode.IsNotNull(theNode))
				{
					umbNodes.Add(theNode);
				}
			}

			// return the list of nodes
			return umbNodes;

		}

		#endregion

		#region ToString

		/// <summary>
		/// Overrides the default ToString method to be more informative of the UmbracoNode object.
		/// </summary>
		/// <returns>More informative ToString method for the current node.</returns>
		public override string ToString()
		{
			return String.Format("{0} ({1})", this.BaseNode.Name, this.ID);
		}

		#endregion

		#region GetNodeUrl
		
		/// <summary>
		/// Gets the redirect url for the current node. If no redirect url exists, then an empty string is returned.
		/// </summary>
		/// <returns>The redirected url for the UmbracoNode, or an empty string if no redirect info is available.</returns>
		private string getNodeRedirectUrl()
		{
			// Check for any URL redirects

			// Get the internalRedirect property
			int internalRedirect = GetPropertyValue<int>("internalRedirect", 0);
			string externalRedirect = GetPropertyValue("externalRedirect");
			if (internalRedirect > 0)
			{
				// return the URL to the umbraco node.
				return umbraco.library.NiceUrl(internalRedirect);
			}
			else if (!String.IsNullOrEmpty(externalRedirect))
			{
				// return the external url
				return ensureValidUrl(externalRedirect);
			}
			else
			{
				// No redirect info present, return current url link
				return "";
			}
		}

		#endregion
		
		#region Helpers

		/// <summary>
		/// Ensures that a URL is valid by checking for a protocol at the start of the string, if one is not found, then prepend http:// and return the url.
		/// </summary>
		/// <param name="url">The URL to check.</param>
		/// <returns>The validated URL string.</returns>
		private static string ensureValidUrl(string url)
		{
			// If the url is empty, return empty
			if (String.IsNullOrEmpty(url))
			{
				return "";
			}

			// If there is no protocol at the start of the string, then add the default http://
			if (!Regex.IsMatch(url, "^[a-zA-Z]+://.*$", RegexOptions.IgnoreCase))
			{
				url = String.Format("http://{0}", url);
			}

			// return the checked url
			return url;

		}

		#endregion

	}
}
